//@author A0087191Y
package net.mysoc.w111j.service.filters;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.mysoc.w111j.model.Expense;

/**
 * A simple text filter matching expense names and details to the given search phrase.
 *
 * The search phrase is a list of keywords, separated by spaces. Keywords can be in quotes to support multiple words.
 */
public class TextExpenseFilter implements ExpenseFilter {
    // Regular expression to match a single unquoted word, or a quoted sequence of words
    private static final Pattern pattern = Pattern.compile("\\b\\w+?\\b|\".+?\"");

    private final String searchPhrase;
    private final List<Pattern> prefixMatchKeywords;
    private final List<Pattern> strictMatchKeywords;

    public TextExpenseFilter(String searchPhrase) {
        if (searchPhrase == null) {
            throw new NullPointerException("Search phrase should not be null");
        }
        this.searchPhrase = searchPhrase.trim();
        if (this.searchPhrase.isEmpty()) {
            throw new NullPointerException("Search phrase should not be empty");
        }

        prefixMatchKeywords = new ArrayList<>();
        strictMatchKeywords = new ArrayList<>();
        Matcher m = pattern.matcher(searchPhrase);
        while (m.find()) {
            String keyword = m.group();
            if (keyword.indexOf('"') == -1) { // this is a prefix match keyword
                // Only allow matches on a word boundary
                prefixMatchKeywords.add(Pattern.compile("\\b" + keyword, Pattern.CASE_INSENSITIVE));
            } else { // strict match keyword
                // Strip away quotation marks, then leading/trailing spaces
                keyword = keyword.replace("\"", "").trim();
                // Only allow matches between word boundaries
                strictMatchKeywords.add(Pattern.compile("\\b" + keyword + "\\b", Pattern.CASE_INSENSITIVE));
            }
        }

        if (prefixMatchKeywords.isEmpty() && strictMatchKeywords.isEmpty()) {
            throw new IllegalArgumentException("No keywords found in search phrase");
        }
    }

    @Override
    public boolean shouldSelect(Expense expense) {
    	// Check if the expense contains every keyword
    	for (Pattern p : prefixMatchKeywords) {
    	    if (!p.matcher(expense.getName()).find() && !p.matcher(expense.getDetails()).find()) {
                return false;
            }
        }
        for (Pattern p : strictMatchKeywords) {
            if (!p.matcher(expense.getName()).find() && !p.matcher(expense.getDetails()).find()) {
                return false;
            }
        }
        return true;
    }

    @Override
    public String getDescription() {
        return "Results for \"" + searchPhrase + "\"";
    }
}
